package com.own.business.exam.business.service.impl;

import com.own.business.exam.business.service.ExamPaperService;
import com.own.business.exam.business.service.ExamRuleAccountService;
import com.own.business.exam.business.service.ExamRuleQuestionCountService;
import com.own.business.exam.business.service.ExamRuleQuestionService;
import com.own.business.exam.business.service.ExamRuleService;
import com.own.business.exam.business.service.ExamService;
import com.own.business.exam.business.util.ExamRuleUtil;
import com.own.business.exam.business.util.ExamUtil;
import com.own.business.exam.common.em.CustomReplyEnvironment;
import com.own.business.exam.common.exception.ExamException;
import com.own.business.exam.entity.bo.ExamPaperBo;
import com.own.business.exam.entity.map.ExamPaperMapVo;
import com.own.business.exam.entity.po.ExamPaper;
import com.own.business.exam.entity.po.ExamRule;
import com.own.business.exam.entity.po.ExamRuleQuestion;
import com.own.business.exam.entity.po.ExamRuleQuestionCount;
import com.own.business.exam.entity.query.ExamPaperQuery;
import com.own.business.exam.entity.vo.ExamPaperVo;
import com.own.business.exam.mapper.ExamPaperMapper;
import com.own.business.question.common.em.QuestionType;
import com.own.business.question.entity.model.PracticeConfig;
import com.own.business.question.entity.po.Practice;
import com.own.business.question.entity.vo.PracticeVo;
import com.own.business.question.open.PracticeOpenMethod;
import com.own.component.common.base.service.impl.AbstractBaseService;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.stream.Collectors;

/**
 * ExamPaperServiceImpl
 *
 * @author chenxueli
 * @date 2021/05/18 14:43:47
 */
@Slf4j
@Service
public class ExamPaperServiceImpl extends AbstractBaseService<
        ExamPaper,
        ExamPaperBo,
        ExamPaperVo,
        ExamPaperMapVo,
        ExamPaperQuery,
        ExamPaperMapper
        > implements ExamPaperService {

    @Resource
    private ExamPaperMapper examPaperMapper;

    @Resource
    private ExamService examService;
    @Resource
    private ExamRuleService examRuleService;
    @Resource
    private ExamRuleQuestionService examRuleQuestionService;
    @Resource
    private ExamRuleAccountService examRuleAccountService;
    @Resource
    private ExamRuleQuestionCountService examRuleQuestionCountService;

    @Resource
    private PracticeOpenMethod practiceOpenMethod;

    @Resource
    private ExamUtil examUtil;
    @Resource
    private ExamRuleUtil examRuleUtil;

    /**
     * 提交试卷
     *
     * @param userId 待获取考试的用户id
     * @param examId 考试id
     * @return 试卷信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ExamPaperVo submit(@NonNull Long userId, Long examId) {
        // 获取考试信息
        var exam = examService.getById(examId);
        // 获取试卷信息
        var paper = getOneByWrapper(lambdaQueryWrapper()
                .eq(ExamPaper::getFkExamId, examId)
                .eq(ExamPaper::getFkUserId, userId)
                .orderByDesc(ExamPaper::getGmtCreate)
        );
        if (paper == null) {
            throw new ExamException("您还没有获取试卷信息，无法提交");
        }
        var practice = practiceOpenMethod.checkPractice(paper.getFkPracticeId()).getData();
        var paperVo = new ExamPaperVo(paper);
        paperVo.setPractice(practice);
        update(paperVo.toPo());
        return paperVo;
    }

    /**
     * 根据考试id获取试卷信息
     *
     * @param userId  待获取考试的用户id
     * @param account 待获取考试的用户账号
     * @param examId  考试id
     * @return 试卷信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ExamPaperVo getPaperByExamId(@NonNull Long userId, @NonNull String account, Long examId) {
        // 获取考试信息
        var exam = examService.getById(examId);
        // 获取试卷信息
        ExamPaper paper = getOneByWrapper(lambdaQueryWrapper()
                .eq(ExamPaper::getFkExamId, examId)
                .eq(ExamPaper::getFkUserId, userId)
                .orderByDesc(ExamPaper::getGmtCreate)
        );
        if (paper != null) {
            // 判断试卷是否已经完成作答
            if (exam.getIsRepeat() != 1 || paper.getStatus() != 1) {
                return getById(paper.getId());
            }
        }
        // 判断是否可以进行考试
        examUtil.checkExamBuildPaper(exam);
        // 获取所有的规则信息
        var ruleList = examRuleService.listEqual(ExamRule::getFkExamId, examId);
        // 验证匹配规则
        var rule = examRuleUtil.matchRule(ruleList, account);
        if (rule == null) {
            throw new ExamException("没有匹配到可选的规则，无法进行考试");
        }
        paper = new ExamPaper().setFkUserId(userId).setFkExamId(examId).setFkRuleId(rule.getId());
        var vo = add(paper).getData();
        afterGetById(vo);
        vo.toPo(paper);
        update(paper);
        return vo;
    }

    /**
     * 根据id获取信息之后处理的信息
     *
     * @param vo vo对象
     * @return 处理的vo对象
     */
    @Override
    public ExamPaperVo afterGetById(ExamPaperVo vo) {
        // 查询练习信息
        PracticeVo practice;
        if (vo.getPracticeId() != null) {
            practice = practiceOpenMethod.getById(vo.getPracticeId());
        } else {
            // 获取规则id
            var rule = examRuleService.getPoById(vo.getRuleId());
            // 配置规则
            var config = new PracticeConfig(CustomReplyEnvironment.EXAM)
                    .setQuestionIdList(() -> examRuleQuestionService.listEqual(ExamRuleQuestion::getFkRuleId, rule.getId())
                            .stream()
                            .map(ExamRuleQuestion::getFkQuestionId)
                            .collect(Collectors.toList())
                    );
            config.setShuffle(false);
            config.setNeedAnswer(true);
            // 获取当前规则的高级数量规则
            var countList = examRuleQuestionCountService.listEqual(ExamRuleQuestionCount::getFkRuleId, rule.getId());
            // 判断是否有规则高级的数量信息，如果没有的话直接使用规则里的题目数量
            if (countList.size() > 0) {
                countList.stream()
                        .filter(item -> QuestionType.has(item.getType()))
                        .forEach(item -> {
                            config.putType(QuestionType.get(item.getType()), item.getNumber());
                        });
            } else {
                config.putType(QuestionType.All, rule.getNumber());
            }
            practice = practiceOpenMethod.getPractice(vo.getId(), vo.getUserId(), config).getData();
        }
        vo.setPractice(practice);
        // 查询考试信息，判断要不要显示信息
        var exam = examService.getById(vo.getExamId());
        examUtil.checkPracticeShow(exam, practice);
        return super.afterGetById(vo);
    }

    /**
     * 根据考试id获取试卷的分数信息
     *
     * @param userId 待获取考试分数的用户id
     * @param examId 考试id
     * @return 分数信息
     */
    @Override
    public BigDecimal getScoreByExamId(@NonNull Long userId, Long examId) {
        var paper = getResultPaperByExamId(userId, examId);
        return paper == null ? null : paper.getScore();
    }

    /**
     * 根据考试id获取试卷信息（不会生成试卷）
     *
     * @param userId 待获取试卷的用户id
     * @param examId 考试id
     * @return 试卷信息
     */
    @Override
    public ExamPaperVo getResultPaperByExamId(@NonNull Long userId, Long examId) {
        // 获取考试信息
        var exam = examService.getById(examId);
        // 获取试卷信息
        var paper = getOneByWrapper(lambdaQueryWrapper()
                .eq(ExamPaper::getFkExamId, examId)
                .eq(ExamPaper::getFkUserId, userId)
                .orderByDesc(ExamPaper::getGmtCreate)
        );
        // 判断获取分数的时机
        if (paper == null || paper.getStatus() != 1) {
            return null;
        }
        if (exam.getViewAnswerType() == 3) {
            return null;
        }
        if (exam.getViewAnswerType() == 2 && LocalDateTime.MAX.isBefore(exam.getViewAnswerTime())) {
            return null;
        }
        return new ExamPaperVo(paper);
    }

    /**
     * 根据考试id获取试卷的排名信息
     *
     * @param userId 待获取考试成绩排名的用户id
     * @param examId 考试id
     * @return 分数信息
     */
    @Override
    public Long getRankByExamId(@NonNull Long userId, Long examId) {
        var paper = getResultPaperByExamId(userId, examId);
        if (paper == null) {
            return null;
        }
        var wrapper = lambdaQueryWrapper()
                .eq(ExamPaper::getFkExamId, examId)
                .eq(ExamPaper::getStatus, Practice.Status.ANSWER)
                .ge(ExamPaper::getScore, paper.getScore())
                .lt(ExamPaper::getSubmitTime, paper.getSubmitTime());
        // 最后的排名需要加一才是真实排名
        return examPaperMapper.selectCount(wrapper) + 1;
    }
}
